/*-------------------------------------------------------------------------
 *
 * be-secure.c
 *	  与为前端建立安全连接相关的函数。
 *	  安全连接应提供机密性、消息完整性和端点身份验证。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/libpq/be-secure.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#include <arpa/inet.h>
#endif

#include "libpq/libpq.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/ipc.h"
#include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/memutils.h"

char	   *ssl_library;
#ifdef FDDGM
bool       EnableTLCP;//是否走TLCP 认证协议
char	   *ssl_enccert_file;//服务端加密证书文件
char	   *ssl_enckey_file;//服务端加密密钥文件
char	   *ssl_encpassphrase_command;//tlcp加密证书密钥被加密时，用于解密的命令
#endif
char	   *ssl_cert_file;
char	   *ssl_key_file;
char	   *ssl_ca_file;
char	   *ssl_crl_file;
char	   *ssl_crl_dir;
char	   *ssl_dh_params_file;
char	   *ssl_passphrase_command;
bool		ssl_passphrase_command_supports_reload;

#ifdef USE_SSL
bool		ssl_loaded_verify_locations = false;
#endif

/* GUC variable controlling SSL cipher list */
char	   *SSLCipherSuites = NULL;

/* GUC variable for default ECHD curve. */
char	   *SSLECDHCurve;

/* GUC variable: if false, prefer client ciphers */
bool		SSLPreferServerCiphers;

int			ssl_min_protocol_version;
int			ssl_max_protocol_version;

/* ------------------------------------------------------------ */
/*			 所有安全会话的共用过程			*/
/* ------------------------------------------------------------ */

/*
 *	初始化全局上下文。
 *
 * 如果 isServerStart 为 true，报告任何错误为 FATAL（这样我们就不会返回）。
 * 否则，以 LOG 级别记录错误并返回 -1 表示出现问题，
 * 如果有，保留旧的 SSL 状态。返回 0 表示正常。
 */
int secure_initialize(bool fc_isServerStart)
{
#ifdef USE_SSL
	return be_tls_init(fc_isServerStart);
#else
	return 0;
#endif
}

/*
 *	销毁全局上下文（如果有的话）。
 */
void secure_destroy(void)
{
#ifdef USE_SSL
	be_tls_destroy();
#endif
}

/*
 * 表示我们是否已加载根 CA 存储以验证证书
 */
bool secure_loaded_verify_locations(void)
{
#ifdef USE_SSL
	return ssl_loaded_verify_locations;
#else
	return false;
#endif
}

/*
 *	尝试协商安全会话。
 */
int secure_open_server(Port *fc_port)
{
	int			fc_r = 0;

#ifdef USE_SSL
	fc_r = be_tls_open_server(fc_port);

	ereport(DEBUG2,
			(errmsg_internal("SSL connection from DN:\"%s\" CN:\"%s\"",
							 fc_port->peer_dn ? fc_port->peer_dn : "(anonymous)",
							 fc_port->peer_cn ? fc_port->peer_cn : "(anonymous)")));
#endif

	return fc_r;
}

/*
 *	关闭安全会话。
 */
void secure_close(Port *fc_port)
{
#ifdef USE_SSL
	if (fc_port->ssl_in_use)
		be_tls_close(fc_port);
#endif
}

/*
 *	从安全连接中读取数据。
 */
ssize_t secure_read(Port *fc_port, void *fc_ptr, size_t fc_len)
{
	ssize_t		fc_n;
	int			fc_waitfor;

	/* 处理任何已挂起的中断条件。 */
	ProcessClientReadInterrupt(false);

retry:
#ifdef USE_SSL
	fc_waitfor = 0;
	if (fc_port->ssl_in_use)
	{
		fc_n = be_tls_read(fc_port, fc_ptr, fc_len, &fc_waitfor);
	}
	else
#endif
#ifdef ENABLE_GSS
	if (fc_port->gss && fc_port->gss->enc)
	{
		fc_n = be_gssapi_read(fc_port, fc_ptr, fc_len);
		fc_waitfor = WL_SOCKET_READABLE;
	}
	else
#endif
	{
		fc_n = secure_raw_read(fc_port, fc_ptr, fc_len);
		fc_waitfor = WL_SOCKET_READABLE;
	}

	/* 在阻塞模式下，等待套接字准备好 */
	if (fc_n < 0 && !fc_port->noblock && (errno == EWOULDBLOCK || errno == EAGAIN))
	{
		WaitEvent	fc_event;

		Assert(fc_waitfor);

		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, fc_waitfor, NULL);

		WaitEventSetWait(FeBeWaitSet, -1 /* 无超时 */ , &fc_event, 1,
						 WAIT_EVENT_CLIENT_READ);

		/*
		 * 如果主进程已死，则继续运行是不安全的，
		 * 因为主进程的工作是杀死我们，如果其他后端
		 * 非正常退出。此外，在这种状态下我们运行不会很好；
		 * 辅助进程如 walwriter 和 bgwriter 会退出，因此
		 * 性能可能会下降。最后，如果我们不退出，pg_ctl 将会
		 * 无法在没有人工干预的情况下重启主进程，因此无法
		 * 接受新的连接。退出为主进程重启清理了工作环境。
		 *
		 * （注意，我们仅在否则需要在我们的闩锁上睡眠时进行此检查。
		 * 如果主进程在查询中途被杀死，或者甚至在多个查询中
		 * 被杀死，我们仍然可能会继续运行一段时间，如果
		 * 我们从不需要等待读取。我们不想浪费太多循环去检查
		 * 这种非常罕见的情况，在大多数情况下这应该会让
		 * 我们迅速退出。）
		 */
		if (fc_event.events & WL_POSTMASTER_DEATH)
			ereport(FATAL,
					(errcode(ERRCODE_ADMIN_SHUTDOWN),
					 errmsg("terminating connection due to unexpected postmaster exit")));

		/* 处理中断。 */
		if (fc_event.events & WL_LATCH_SET)
		{
			ResetLatch(MyLatch);
			ProcessClientReadInterrupt(true);

			/*
			 * 我们将重试读取。最有可能的是它会立即返回
			 * 因为仍然没有数据可用，我们会等待套接字
			 * 再次准备好。
			 */
		}
		goto retry;
	}

	/*
	 * 处理在成功（或非阻塞，
	 * 或硬失败）读取期间发生的中断。
	 */
	ProcessClientReadInterrupt(false);

	return fc_n;
}

ssize_t secure_raw_read(Port *fc_port, void *fc_ptr, size_t fc_len)
{
	ssize_t		fc_n;

	/*
	 * 尝试在不阻塞的情况下从套接字读取。如果成功，
	 * 我们完成了，否则我们会使用闩锁机制等待套接字。
	 */
#ifdef WIN32
	pgwin32_noblock = true;
#endif
	fc_n = recv(fc_port->sock, fc_ptr, fc_len, 0);
#ifdef WIN32
	pgwin32_noblock = false;
#endif

	return fc_n;
}


/*
 *	向安全连接写入数据。
 */
ssize_t secure_write(Port *fc_port, void *fc_ptr, size_t fc_len)
{
	ssize_t		fc_n;
	int			fc_waitfor;

	/* 处理任何已挂起的中断条件。 */
	ProcessClientWriteInterrupt(false);

retry:
	fc_waitfor = 0;
#ifdef USE_SSL
	if (fc_port->ssl_in_use)
	{
		fc_n = be_tls_write(fc_port, fc_ptr, fc_len, &fc_waitfor);
	}
	else
#endif
#ifdef ENABLE_GSS
	if (fc_port->gss && fc_port->gss->enc)
	{
		fc_n = be_gssapi_write(fc_port, fc_ptr, fc_len);
		fc_waitfor = WL_SOCKET_WRITEABLE;
	}
	else
#endif
	{
		fc_n = secure_raw_write(fc_port, fc_ptr, fc_len);
		fc_waitfor = WL_SOCKET_WRITEABLE;
	}

	if (fc_n < 0 && !fc_port->noblock && (errno == EWOULDBLOCK || errno == EAGAIN))
	{
		WaitEvent	fc_event;

		Assert(fc_waitfor);

		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, fc_waitfor, NULL);

		WaitEventSetWait(FeBeWaitSet, -1 /* 无超时 */ , &fc_event, 1,
						 WAIT_EVENT_CLIENT_WRITE);

		/* 请参见 secure_read 中的注释。 */
		if (fc_event.events & WL_POSTMASTER_DEATH)
			ereport(FATAL,
					(errcode(ERRCODE_ADMIN_SHUTDOWN),
					 errmsg("terminating connection due to unexpected postmaster exit")));

		/* 处理中断。 */
		if (fc_event.events & WL_LATCH_SET)
		{
			ResetLatch(MyLatch);
			ProcessClientWriteInterrupt(true);

			/*
			 * 我们将重试写入。最有可能的是它会立即返回
			 * 因为仍然没有可用的缓冲区空间，我们会等待
			 * 套接字再次准备好。
			 */
		}
		goto retry;
	}

	/*
	 * 处理在成功（或非阻塞，
	 * 或硬失败）写入期间发生的中断。
	 */
	ProcessClientWriteInterrupt(false);

	return fc_n;
}

ssize_t secure_raw_write(Port *fc_port, const void *fc_ptr, size_t fc_len)
{
	ssize_t		fc_n;

#ifdef WIN32
	pgwin32_noblock = true;
#endif
	fc_n = send(fc_port->sock, fc_ptr, fc_len, 0);
#ifdef WIN32
	pgwin32_noblock = false;
#endif

	return fc_n;
}
